/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */
package com.googlecode.ivybeans.module;

import com.googlecode.ivybeans.facade.IvyLibrary;
import com.googlecode.ivybeans.facade.IvyLibraryException;
import com.googlecode.ivybeans.facade.ResolvedIvyLibrary;
import com.googlecode.ivybeans.module.classpath.ClassPathScope;
import com.googlecode.ivybeans.module.classpath.ScopeConfigurationMapping;
import com.googlecode.ivybeans.module.util.ILCListener;
import com.googlecode.ivybeans.module.util.FileHelper;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.logging.Logger;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.progress.ProgressHandleFactory;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectManager;
import org.openide.ErrorManager;
import org.openide.filesystems.FileChangeAdapter;
import org.openide.filesystems.FileEvent;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileRenameEvent;
import org.openide.filesystems.FileUtil;
import org.openide.util.NbBundle;
import org.openide.util.RequestProcessor;

/**
 *
 * @author lforet
 */
public class IvyLibraryController {

    private static final Logger LOG = Logger.getLogger("com.googlecode.ivybeans.module");
    private final Project project;
    private boolean ivyEnableState = false;
    private boolean autoResolve = false;
    private final IvyLibrary ivyLibrary = new IvyLibrary();
    private final IvyResolver ivyResolver = new IvyResolver();
    private final IvyExtender ivyExtender = new IvyExtender();
    private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
    private final DescriptorFileListener descriptorFileListener = new DescriptorFileListener();
    private final SettingsFileListener settingsFileListener = new SettingsFileListener();
    private ScopeConfigurationMapping configurationMapping;
//    public final static String[] DEFAULT_CONF = new String[]{"*"};
    public final static String SETTINGS_CHANGE = "ivySettingsChange";
    public final static String DESCRIPTOR_CHANGE = "ivyDescriptorChange";
    public final static String SETTINGS_CONTENT_CHANGE = "ivySettingsContentChange";
    public final static String DESCRIPTOR_CONTENT_CHANGE = "ivyDescriptorContentChange";
    public final static String IVY_ENABLE_STATE_CHANGE = "ivyEnableStateChange";
    public final static String CONFIGURATION_CHANGE = "ivyConfigurationChange";
    public final static String IVY_LIBRARY_RESOLVED = "ivyLibraryResolved";
    private final Object lock = new Object();

    public IvyLibraryController(Project project, boolean isEnabled, String descriptorPath, String settingsPath, ScopeConfigurationMapping configurationMapping) {
        assert project != null;
        this.project = project;
        setEnableState(isEnabled);
        setDescriptorFile(FileHelper.getInstance().getFile(project.getProjectDirectory(), descriptorPath));
        this.configurationMapping = configurationMapping;
        setSettings(settingsPath);
        addPropertyChangeListener(new ILCListener());
        addPropertyChangeListener(ivyExtender);
        addPropertyChangeListener(ivyResolver);
    }

//    public Project getProject() {
//        return project;
//    }
    public void setEnableState(boolean enableState) {
        boolean oldIvyEnableState = ivyEnableState;
        ivyEnableState = enableState;
        if (oldIvyEnableState != ivyEnableState) {
            pcs.firePropertyChange(IVY_ENABLE_STATE_CHANGE, oldIvyEnableState, ivyEnableState);
        }
    }

    public boolean isIvyEnabled() {
        return ivyEnableState;
    }

    public boolean isAutoResolve() {
        return autoResolve;
    }

    public void setAutoResolve(boolean autoResolve) {
        this.autoResolve = autoResolve;
    }

    public void setDescriptorFile(File file) {
        try {
            File oldFile = getDescriptorFile();
            if (oldFile != null) {
                FileObject oldDescriptorFO = FileUtil.toFileObject(oldFile);
                if (oldDescriptorFO != null) {
                    oldDescriptorFO.removeFileChangeListener(descriptorFileListener);
                }
            }
            URL descriptorURL = null;
            if (file != null && file.isFile()) {
                FileObject descriptorFO = FileUtil.toFileObject(file);
                if (descriptorFO != null) {
                    descriptorFO.addFileChangeListener(descriptorFileListener);
                }
                descriptorURL = file.toURI().toURL();
            }

            ivyLibrary.setDescriptorURL(descriptorURL);
            firePropertyChange(DESCRIPTOR_CHANGE, oldFile, file);
        } catch (MalformedURLException ex) {
            ErrorManager.getDefault().notify(ex);
        }
    }

    public File getDescriptorFile() {
        return FileHelper.getInstance().getFile(ivyLibrary.getDescriptorURL());
    }

    public void setSettings(String path) {
        if (path == null || path.length() == 0) {
            setSettingsFile(null);
            return;
        }
        File file = FileHelper.getInstance().getFile(project.getProjectDirectory(), path);
        if (file != null) {
            setSettingsFile(file);
        } else {
            try {
                URL url = new URL(path);
                setSettingsURL(url);
            } catch (MalformedURLException ex) {
                ErrorManager.getDefault().notify(new Exception(path, ex));
            }
        }
    }

    public void setSettingsFile(File file) {
        try {
            File oldFile = getSettingsFile();
            if (oldFile != null) {
                FileObject oldSettingsFO = FileUtil.toFileObject(oldFile);
                if (oldSettingsFO != null) {
                    oldSettingsFO.removeFileChangeListener(settingsFileListener);
                }
            }
            URL settingsURL = null;
            if (file != null && file.isFile()) {
                FileObject settingsFO = FileUtil.toFileObject(file);
                if (settingsFO != null) {
                    settingsFO.addFileChangeListener(settingsFileListener);
                }
                settingsURL = file.toURI().toURL();
            }
            ivyLibrary.setSettingsURL(settingsURL);
            firePropertyChange(SETTINGS_CHANGE, oldFile, file);
        } catch (MalformedURLException ex) {
            ErrorManager.getDefault().notify(ex);
        }
    }

    protected void setSettingsURL(URL url) {
        File oldFile = getSettingsFile();
        if (oldFile != null) {
            FileObject oldSettingsFO = FileUtil.toFileObject(oldFile);
            if (oldSettingsFO != null) {
                oldSettingsFO.removeFileChangeListener(settingsFileListener);
            }
        }
        ivyLibrary.setSettingsURL(url);
        firePropertyChange(SETTINGS_CHANGE, oldFile, null);
    }

    public File getSettingsFile() {
        return FileHelper.getInstance().getFile(getSettingsURL());
    }

    public URL getSettingsURL() {
        return "jar".equalsIgnoreCase(ivyLibrary.getSettingsURL().getProtocol())
                ? null
                : ivyLibrary.getSettingsURL();
    }

    public void triggerIvyResolution() {
        ivyResolver.triggerResolution();
    }

    public void addPropertyChangeListener(PropertyChangeListener propertyChangeListener) {
        pcs.addPropertyChangeListener(propertyChangeListener);
    }

    public void removePropertyChangeListener(PropertyChangeListener propertyChangeListener) {
        pcs.removePropertyChangeListener(propertyChangeListener);
    }

    public IvyLibrary getIvyLibrary() {
        return ivyLibrary;
    }

    public ResolvedIvyLibrary getResolvedLibrary(ClassPathScope scope) {
        return getConfigurationMapping().getResolvedIvyLibrary(scope);
    }

    public ScopeConfigurationMapping getConfigurationMapping() {
        if (configurationMapping == null) {
            configurationMapping = ScopeConfigurationMapping.createDefault();
        }
        return configurationMapping;
    }

    public void setConfiguration(ClassPathScope scope, String[] confs) {
//        ConfigurationMapping previous = configurationMapping;
//        configurationMapping = new ConfigurationMapping();
        String[] oldConfs = getConfigurationMapping().getConfiguration(scope);
        getConfigurationMapping().put(scope, confs, ResolvedIvyLibrary.EMPTY_RESOLVED);
        firePropertyChange(CONFIGURATION_CHANGE, oldConfs, confs);
    }

    private class IvyResolver implements PropertyChangeListener {

        private RequestProcessor.Task ivyResolverTask;

        public IvyResolver() {

            ivyResolverTask = RequestProcessor.getDefault().create(new Runnable() {

                public void run() {
                    log("IvyResolver.run()");
                    synchronized (lock) {
                        if (!isIvyEnabled()) {
                            return;
                        }
                        ProgressHandle progressHandle = ProgressHandleFactory.createHandle(NbBundle.getMessage(IvyLibraryController.class, "LBL_Resolving_Progress"));
                        try {
                            IOTabProvider.getInstance().getIO(project).select();
                            IOTabProvider.getInstance().getIO(project).getOut().reset();
                            ivyLibrary.setOut(IOTabProvider.getInstance().getIO(project).getOut());
                            int numberOfSteps = ClassPathScope.values().length;
                            progressHandle.start(numberOfSteps);

                            int currentStep = 0;
                            for (ClassPathScope scope : ClassPathScope.values()) {
                                progressHandle.progress(scope.name(), currentStep++);
                                String[] confs = getConfigurationMapping().getConfiguration(scope);
                                IOTabProvider.getInstance().getIO(project).getOut().write(String.format("Resolving %s scope ...\n", scope));
                                ResolvedIvyLibrary resolvedIvyLibrary = ivyLibrary.resolve(confs);
                                getConfigurationMapping().put(scope, confs, resolvedIvyLibrary);
                            }

                            pcs.firePropertyChange(IVY_LIBRARY_RESOLVED, null, null);
                        } catch (IvyLibraryException ex) {
                            ErrorManager.getDefault().notify(ex);
                        } catch (IOException ex) {
                            ErrorManager.getDefault().notify(ex);
                        } finally {
                            progressHandle.finish();
                        }
                    }
                }
            });
        }

        public void propertyChange(PropertyChangeEvent evt) {
            if (!isAutoResolve()) {
                return;
            }
            if (DESCRIPTOR_CHANGE.equals(evt.getPropertyName()) || DESCRIPTOR_CONTENT_CHANGE.equals(evt.getPropertyName()) || SETTINGS_CHANGE.equals(evt.getPropertyName()) || CONFIGURATION_CHANGE.equals(evt.getPropertyName())) {
                triggerResolution();
            }
        }

        public void triggerResolution() {
            log("triggerResolution()");
            ivyResolverTask.schedule(300);
        }
    }

    private class IvyExtender implements PropertyChangeListener {

        private RequestProcessor.Task extendTask;

        public IvyExtender() {

            extendTask = RequestProcessor.getDefault().create(new Runnable() {

                public void run() {
                    synchronized (lock) {
                        try {
                            ProjectManager.mutex().postWriteRequest(new Runnable() {
                                public void run() {
                                    log("IvyExtender.run()");
                                    if (isIvyEnabled()) {
                                        ProjectConfigurationHelper.getInstance().store(project);
                                        IvyBuildHelper.getInstance().createIvyImpl(project, getDescriptorFile(), getSettingsFile(), getSettingsURL());
                                    } else {
                                        ProjectConfigurationHelper.getInstance().clean(project);
                                        IvyBuildHelper.getInstance().clean(project);
                                    }
                                    try {
                                        ProjectManager.getDefault().saveProject(project);
                                    } catch (IOException ex) {
                                        ErrorManager.getDefault().notify(ex);
                                  }
                                }
                            });
                        } catch (IllegalArgumentException ex) {
                            ErrorManager.getDefault().notify(ex);
                        }
                    }
                }
            });

        }

        public void propertyChange(PropertyChangeEvent evt) {
            if (DESCRIPTOR_CHANGE.equals(evt.getPropertyName()) ||
                    SETTINGS_CHANGE.equals(evt.getPropertyName()) ||
                    IVY_ENABLE_STATE_CHANGE.equals(evt.getPropertyName()) ||
                    CONFIGURATION_CHANGE.equals(evt.getPropertyName())) {
                triggerExtend();
            }
        }

        private void triggerExtend() {
            log("triggerExtend()");
            extendTask.schedule(100);
        }
    }

    private class DescriptorFileListener extends FileChangeAdapter {

        @Override
        public void fileChanged(FileEvent fe) {
            pcs.firePropertyChange(DESCRIPTOR_CONTENT_CHANGE, null, null);
        }

        @Override
        public void fileDeleted(FileEvent fe) {
            setDescriptorFile(null);
        }

        @Override
        public void fileRenamed(FileRenameEvent fe) {
            setDescriptorFile(FileUtil.toFile(fe.getFile()));
        }
    }

    private class SettingsFileListener extends FileChangeAdapter {

        @Override
        public void fileChanged(FileEvent fe) {
            pcs.firePropertyChange(SETTINGS_CONTENT_CHANGE, null, null);
        }

        @Override
        public void fileDeleted(FileEvent fe) {
            setSettingsFile(null);
        }

        @Override
        public void fileRenamed(FileRenameEvent fe) {
            setSettingsFile(FileUtil.toFile(fe.getFile()));
        }
    }

    private void log(String message) {
        LOG.fine(String.format("%s : %s", project.getProjectDirectory().getName(), message));
    }

    private void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
        if ((oldValue == null && newValue != null) ||
                (oldValue != null && !oldValue.equals(newValue))) {
            pcs.firePropertyChange(propertyName, oldValue, newValue);
        }
    }
}
